/* Turska Framework/UI library (Turs2)
 * Copyright 2003-2010 Jetro Lauha
 * All rights reserved.
 * Web: http://iki.fi/jetro/turska/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of EITHER:
 *   (1) The GNU Lesser General Public License as published by the Free
 *       Software Foundation; either version 2.1 of the License, or (at
 *       your option) any later version. The text of the GNU Lesser
 *       General Public License is included with this library in the
 *       file LICENSE-LGPL.txt.
 *   (2) The BSD-style license that is included with this library in
 *       the file LICENSE-BSD.txt.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the files
 * LICENSE-LGPL.txt and LICENSE-BSD.txt for more details.
 *
 * $Id: T2DynamicArray.cpp 28 2012-07-28 17:03:06Z trzntaat $
 * $Revision: 28 $
 */

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "Turs2.h"
#include "T2DynamicArray.h"


#ifndef ASSERT
#define ASSERT assert
#endif


namespace turska
{


DynamicArrayImpl::DynamicArrayImpl(const I32 elementSize,
                                   I32 initialReserve,
                                   bool multiplySizeToGrow,
                                   I32 growAmount) :
    mElementSize(elementSize),
    mData(NULL),
    mElementCount(0),
    mReservedCount(0),
    mGrowAmount(growAmount),
    mMultiplySizeToGrow(multiplySizeToGrow)
{
    init(initialReserve);
}


DynamicArrayImpl::~DynamicArrayImpl()
{
    delete[] mData;
    mData = NULL;
    mElementCount = 0;
}


void DynamicArrayImpl::init(I32 initialReserve)
{
    // Init should only be called when array is not yet in use.
    ASSERT(mData == NULL);
    ASSERT(mReservedCount == 0);
    ASSERT(mElementCount == 0);

    reserve(initialReserve);
}


I32 DynamicArrayImpl::size() const
{
    return mElementCount;
}


I32 DynamicArrayImpl::getReservedSize() const
{
    return mReservedCount;
}


void DynamicArrayImpl::getConfiguration(bool &multiplySizeToGrow,
                                        I32 &growAmount)
{
    multiplySizeToGrow = mMultiplySizeToGrow;
    growAmount = mGrowAmount;
}


bool DynamicArrayImpl::reserve(I32 n)
{
    if (n <= mReservedCount)
        return true;

    I8 *newData = new I8[n * mElementSize];
    if (newData == NULL)
        return false;

    if (mData != NULL)
    {
        memcpy(newData, mData, mElementCount * mElementSize);
        delete[] mData;
    }
    mData = newData;
    mReservedCount = n;

    return true;
}


bool DynamicArrayImpl::isEmpty()
{
    return mElementCount == 0;
}


void DynamicArrayImpl::clear()
{
    mElementCount = 0;
}


void * DynamicArrayImpl::get(I32 index) const
{
    ASSERT(mElementCount > 0);
    ASSERT(index >= 0 && index < mElementCount);
    return &mData[index * mElementSize];
}


void * DynamicArrayImpl::getLast() const
{
    ASSERT(mElementCount > 0);
    return &mData[(mElementCount - 1) * mElementSize];
}


bool DynamicArrayImpl::set(I32 index, void *element)
{
    ASSERT(index >= 0 && index <= mElementCount);

    if (index == mElementCount)
    {
        return add(element);
    }

    memcpy(&mData[index * mElementSize], element, mElementSize);

    return true;
}


I32 DynamicArrayImpl::find(void *element, I32 index)
{
    ASSERT(index >= 0 && index <= mElementCount);

    if (index >= mElementCount)
        return -1;

    I32 offset = index * mElementSize;
    while (index < mElementCount)
    {
        if (memcmp(&mData[offset], element, mElementSize) == 0)
            return index;
        ++index;
        offset += mElementSize;
    }

    return -1;
}


bool DynamicArrayImpl::add(void *element)
{
    bool success = growIfNoEmptySlots();
    if (!success)
        return false;

    memcpy(&mData[mElementCount * mElementSize], element, mElementSize);
    ++mElementCount;

    return true;
}


bool DynamicArrayImpl::insert(I32 index, void *element)
{
    ASSERT(index >= 0 && index <= mElementCount);

    if (index == mElementCount)
        return add(element);

    bool success = growIfNoEmptySlots();
    if (!success)
        return false;

    const I32 offset = index * mElementSize;
    memmove(&mData[offset + mElementSize], &mData[offset],
            (mElementCount - index) * mElementSize);

    memcpy(&mData[offset], element, mElementSize);

    ++mElementCount;

    return true;
}


void DynamicArrayImpl::remove(I32 index)
{
    ASSERT(mElementCount > 0);
    ASSERT(index >= 0 && index < mElementCount);

    if (index >= mElementCount)
        return;

    if (index == mElementCount - 1)
    {
        --mElementCount;
        return;
    }

    const I32 offset = index * mElementSize;
    memmove(&mData[offset], &mData[offset + mElementSize],
            (mElementCount - 1 - index) * mElementSize);
    --mElementCount;
}


bool DynamicArrayImpl::remove(I32 start, I32 end)
{
    ASSERT(start >= 0 && start < mElementCount);
    ASSERT(end >= 0 && end <= mElementCount);
    ASSERT(start <= end);

    if (start < 0 || start >= mElementCount)
        return false;
    if (end < 0 || end > mElementCount)
        return false;

    if (start > end)
        return false;
    if (start == end)
        return true;

    const I32 toOffset = start * mElementSize;
    const I32 fromOffset = end * mElementSize;
    memmove(&mData[toOffset], &mData[fromOffset],
            (mElementCount - end) * mElementSize);

    mElementCount -= end - start;

    return true;
}


void DynamicArrayImpl::removeLast()
{
    ASSERT(mElementCount > 0);
    --mElementCount;
}


void DynamicArrayImpl::stdQSort(int (*compare)(const void *, const void *))
{
    ::qsort(mData, mElementCount, mElementSize, compare);
}


bool DynamicArrayImpl::growIfNoEmptySlots()
{
    if (mElementCount < mReservedCount)
        return true;    // there's still unused space

    I32 newReservedCount = mReservedCount;

    if (mMultiplySizeToGrow)
        newReservedCount += mReservedCount * mGrowAmount / 100;
    else
        newReservedCount += mGrowAmount;

    if (newReservedCount == mReservedCount)
        ++newReservedCount;

    return reserve(newReservedCount);
}


} // namespace turska
